#ifndef COLAMENSAJE_H_
#define COLAMENSAJE_H_

#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <errno.h>
#include <string.h>

#include <BaseException.h>
#include <Constantes.h>
#include <File.h>
#include <LockFile.h>

template <class T> class ColaMensaje {
private:
	int m_Id;
	File m_KeyFile;
	LockFile m_LockFile;
	
public:
	ColaMensaje();
	ColaMensaje( const std::string& archivo, 
				 const int creationMode,
				 unsigned int maxMsgInQueue = 0,
				 const char projectId = ProjectId::COLA_MENSAJE );
	~ColaMensaje();
	
	void crear ( const std::string& archivo, const int creationMode, 
				 unsigned int maxMsgInQueue = 0,
				 const char projectId = ProjectId::COLA_MENSAJE);
	int eliminar();
	void destruir();
	
	bool escribir ( const T& dato ) const;
	int leer ( const int tipo,T* buffer ) const;
};

template <class T> ColaMensaje<T> :: ColaMensaje ()
:m_Id(-1)
{
}

template <class T> ColaMensaje<T> :: ColaMensaje ( const std::string& archivo, 
												   const int creationMode, 
												   unsigned int maxMsgInQueue,
												   const char projectId )
:m_Id(-1)
{
	this->crear(archivo, creationMode, projectId);
}

template <class T> ColaMensaje<T> :: ~ColaMensaje ()
{
}

template <class T> void ColaMensaje<T>::crear ( const std::string& archivo, 
												const int creationMode, 
												unsigned int maxMsgInQueue,
												const char projectId )
{
	if(this->m_Id >= 0)
		throw BaseException("La clase esta en uso");
	
	if(archivo.empty())
		throw BaseException("El nombre de archivo no puede ser vacio");
	
	// Creo o verifico que exista o no exista el archivo segun el modo de creacion
	std::string archivoLock = archivo + ".lock";
	m_LockFile.abrir(archivoLock);
	m_LockFile.tomarLock();
	bool creado = m_KeyFile.crear(archivo, creationMode);
	
	key_t clave = ftok ( archivo.c_str(), projectId);
	if ( clave > 0 )
	{
		int msgflag = (creado || creationMode == CreationMode::CREAR_SI_NO_EXISTE) ? 0777|IPC_CREAT : 0777;
		
		this->m_Id = msgget ( clave, msgflag);
		
		if ( this->m_Id < 0 )
		{
			if(creado)
				m_KeyFile.eliminar();
			
			m_LockFile.liberarLock();
			m_LockFile.cerrar();
			
			std::stringstream eMsg;
			eMsg << "Error " << errno << " en msgget(): " << strerror(errno);
			throw BaseException(eMsg.str());
		}
	}
	else
	{
		if(creado)
			m_KeyFile.eliminar();
		
		m_LockFile.liberarLock();
		m_LockFile.cerrar();
		
		std::stringstream eMsg;
		eMsg << "Ocurrio un error al obtener la clave para el archivo " << archivo;
		eMsg << ". Error " << errno << " en ftok(): " << strerror(errno);
		throw BaseException(eMsg.str());
	}
	
	// Verifico si debo setear el limite de mensajes para la cola
	if(creado && maxMsgInQueue > 0)
	{
		msqid_ds buf;
		int opResult = msgctl(m_Id, IPC_STAT, &buf);
		if( opResult < 0 )
		{
			m_KeyFile.eliminar();
			
			m_LockFile.liberarLock();
			m_LockFile.cerrar();
			
			std::stringstream eMsg;
			eMsg << "Ocurrio un error al obtener la dimension de la cola " << archivo;
			eMsg << ". Error " << errno << " en msgctl(): " << strerror(errno);
			throw BaseException(eMsg.str());
		}
		
		buf.msg_qbytes = maxMsgInQueue * sizeof(T);
		opResult = msgctl(m_Id, IPC_SET, &buf);
		if( opResult < 0 )
		{
			m_KeyFile.eliminar();
			
			m_LockFile.liberarLock();
			m_LockFile.cerrar();
			
			std::stringstream eMsg;
			eMsg << "Ocurrio un error al establecer la dimension de la cola " << archivo;
			eMsg << ". Error " << errno << " en msgctl(): " << strerror(errno);
			throw BaseException(eMsg.str());
		}
	}
	
	m_LockFile.liberarLock();
	m_LockFile.cerrar();
}

template <class T> int ColaMensaje<T> :: eliminar ()
{
	int resultado = 0;
	if(this->m_Id >= 0)
	{
		resultado = msgctl ( this->m_Id,IPC_RMID,NULL );
		if(resultado >= 0)
		{
			this->destruir();
			m_KeyFile.eliminar();
			m_LockFile.eliminar();
		}
	}
	
	return resultado;
}

template <class T> void ColaMensaje<T> :: destruir ()
{
	this->m_Id = -1;
}

template <class T> bool ColaMensaje<T> :: escribir ( const T& dato ) const
{
	if(this->m_Id == -1)
		throw BaseException("La cola no fue inicializada");
	
	int resultado = msgsnd ( this->m_Id,static_cast<const void*>(&dato),sizeof(T)-sizeof(long),0 );
	return (resultado == 0);
}

template <class T> int ColaMensaje<T> :: leer ( const int tipo,T* buffer ) const
{
	if(this->m_Id == -1)
		throw BaseException("La cola no fue inicializada");
	
	int resultado = msgrcv ( this->m_Id,static_cast<void *>(buffer),sizeof(T)-sizeof(long),tipo,0 );
	return resultado;
}

#endif /* COLAMENSAJE_H_ */
